package com.vecspace.security.proxy.service;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Value;
import com.vecspace.common.cache.QDualCache;
import com.vecspace.constant.AppConst;
import com.vecspace.core.support.auth.BaseAuthManager;
import com.vecspace.core.support.auth.IAuthManager;
import com.vecspace.security.model.SSOConsts;
import com.vecspace.sso.entity.AppFuncAuth;
import com.vecspace.sso.entity.AppFuncUrl;
import com.vecspace.sso.entity.AppFunctionAuth;
import com.vecspace.sso.entity.AuthTenantType;
import com.vecspace.sso.entity.SSOTenant;
import com.vecspace.sso.entity.SSOTenantEx;
import com.vecspace.sso.entity.SSOUser;
import com.vecspace.sso.entity.UserBase;
import com.vecspace.utils.Assert;
import com.vecspace.web.HttpContextThreadLocal;

/**用户及权限管理的封装
 * 
 * @version: 1.0
 * @author: wanghua
 */
 
public class LoginManager implements IAuthManager{
	
	private static AuthorizeService authorizeService;
	
	static{
		LoginManager loginManager = new LoginManager();
		BaseAuthManager.setAuthManager(loginManager);//此处需要注意，对com.dayu.core.support.auth.BaseAuthManager内的属性进行反向赋值，供BaseRestServlet中进行权限判断使用
	}
	
	
	public AuthorizeService getAuthorizeService() {
		return authorizeService;
	}


	public void setAuthorizeService(AuthorizeService authorizeService) {
		LoginManager.authorizeService = authorizeService;
	}

	
	/**
	 * 判断用户是否已经登录
	 * @return 
	 * @author  wanghua
	 */
	public static boolean isLogon(){
		try {
			HttpSession session = HttpContextThreadLocal.getHttpContext().getHttpSession();
			String loginToken = (String)session.getAttribute(SSOConsts.SESSION_NAME_LOGIN_TOKEN);
			return loginToken != null && loginToken.length() > 0;
		} catch (Exception e) {
			return false;
		}
	}
	
	public static UserBase getUserBaseInfo(String loginId) throws Exception{
		return authorizeService.getUserBaseInfo(loginId);
	}
	
	/**
	 * 根据用户名，密码进行系统登录
	 * 功能描述：
	 * @param userId
	 * @param password
	 * @return 登录成功则返回loginToken，否则返回空字符串
	 * @throws Exception 
	 * @author wanghua
	 */
	public static String login(String loginId, String password) throws Exception{	
		HttpServletRequest request = HttpContextThreadLocal.getHttpContext().getHttpRequest();
		HttpSession session = request.getSession();
		Object randImg = session.getAttribute("rand");
		clearSession(request);//登录之后，要清除session，否则session里面可能会带上上次登录用户的信息。清除session之后要重新登录，在session中记录登录信息。	
		session = request.getSession();
		if(randImg != null) session.setAttribute("rand", randImg);//清除session之后，再恢复session中的randImg，否则验证码出错
		
		String loginToken = authorizeService.login(loginId, password);
		if(loginToken != null && loginToken.length() > 0){
			try{
				session = HttpContextThreadLocal.getHttpContext().getHttpSession();
				session.setAttribute(SSOConsts.SESSION_NAME_LOGIN_TOKEN, loginToken);//必须先设置token，然后再请求其他数据，否则会被认为没有登录。
				
				SSOUser logonUser = authorizeService.getUser(loginId);
				Assert.throwz(logonUser != null, "authorizeService.getUser没有获取到用户信息：loginId =" + loginId);
				Long userId = logonUser.getId();
				String loginId2 = logonUser.getLoginId();
				String loginUserName = logonUser.getUserName();
							
				session.setAttribute(SSOConsts.SESSION_NAME_LOGIN_USER_ID, userId);
				session.setAttribute(SSOConsts.SESSION_NAME_LOGIN_ID, loginId2);
				session.setAttribute(SSOConsts.SESSION_NAME_LOGIN_NAME, loginUserName);
			}catch(Exception e){
				e.printStackTrace();
				if(session != null) {
					session.removeAttribute(SSOConsts.SESSION_NAME_LOGIN_TOKEN);
					session.removeAttribute(SSOConsts.SESSION_NAME_LOGIN_USER_ID);
					session.removeAttribute(SSOConsts.SESSION_NAME_LOGIN_ID);
					session.removeAttribute(SSOConsts.SESSION_NAME_LOGIN_NAME);
				}
			}
		
			return loginToken;
		}else{
			return null;
		}
	}
	
	/**切换登录的租户
	 * 
	 * @param refTenantId
	 * @param tenantType
	 * @throws Exception 
	 * @author  wanghua
	 */
//	public static void loginAsTenant(String refTenantId, String tenantType) throws Exception {
//		String customTenantId = AuthTenantType.getCustomTenantId(refTenantId, tenantType);
//		Long tenantId = authorizeService.loginAsTenant(customTenantId);
//		Assert.throwz(tenantId != null && tenantId > 0, "租户登录错误,customTenantId="+customTenantId);
//		HttpSession session = HttpContextThreadLocal.getHttpContext().getHttpSession();
//		session.setAttribute(SSOConsts.SESSION_NAME_TENANT_ID, tenantId);
//		if(AuthTenantType.PROJECT.equals(tenantType)) {
//			BimLoginManager.setLoginUserProject(session, refTenantId);
//		}else if(AuthTenantType.PROJECT_SECTION.equals(tenantType)) {
//			BimLoginManager.setProjectSectionId(session, refTenantId);
//		}
//	}
	/**清空Session
	 * 
	 * @param request 
	 * @author: wanghua
	 */
	public static void clearSession(HttpServletRequest request){
		//保留saas租户的id（租户id是否发生变化，在SaasTenantFilter中进行判定，如果变化，会清空session）
		HttpSession session = request.getSession();
		if(session == null)return;
		//Object saasTenantIdObj = session.getAttribute(SaasTenant.SaasTenantIdSessionKey);
		
		//用invalidate后，request.getSession(true)后，session也不能使用,因为不能重新创建session
		//session.invalidate();//登录之后，要清除session，否则session里面可能会带上上次登录用户的信息。清除session之后要重新登录，在session中记录登录信息。	
		//session = request.getSession(true);
		
		Enumeration<String> sessionKeys = session.getAttributeNames();
		while(sessionKeys.hasMoreElements()){
			session.removeAttribute(sessionKeys.nextElement());
		}
		
		//session.setAttribute(SaasTenant.SaasTenantIdSessionKey, saasTenantIdObj);
	}
	/**判断loginToken是否有效，有效表示这是一个已经登录的用户 
	 * @param loginToken
	 * @return 有效返回原loginToken，否则返回空字符串
	 * @throws Exception 
	 * @author  wanghua
	 */
	public static boolean checkLoginToken(String loginToken) throws Exception{
		return authorizeService.checkLoginToken(loginToken);
	}
	/**
	 * 注销当前登录用户的所有信息
	 * 清空session
	 * @throws Exception
	 * @author  wanghua
	 */
	public static void logout() throws Exception{
		HttpServletRequest req = HttpContextThreadLocal.getHttpContext().getHttpRequest();
		clearSession(req);
	}
	
	/**
	 * 获取登录后的token
	 * 功能描述：
	 * @return 
	* @author wanghua
	 */
	public static String getLoginTokenFromSession(){
		HttpSession session = HttpContextThreadLocal.getHttpContext().getHttpSession();				
		String loginToken = (String)session.getAttribute(SSOConsts.SESSION_NAME_LOGIN_TOKEN);
		if(loginToken == null) {
			HttpServletRequest request = HttpContextThreadLocal.getHttpContext().getHttpRequest();
			loginToken = request.getParameter(SSOConsts.URL_PARAM_LOGIN_TOKEN);
		}
		return loginToken;
	}
	
	/**
	 * 获取当前登录用户的loginId
	 * @return
	 * @author  wanghua
	 */
	public static String getLoginId(){
		try {
			HttpSession session = HttpContextThreadLocal.getHttpContext().getHttpSession();
			String loginId = (String)session.getAttribute(SSOConsts.SESSION_NAME_LOGIN_ID);
			return loginId;
		} catch (Exception e) {
			return null;
		}
	}
	

	/**获取当前登录用户的loginId，同getLoginId
	 * 
	 * @return 
	 * @author  wanghua
	 * @reviewer  
	 */
	public static Long getLoginUserId(){
		try {
			HttpSession session = HttpContextThreadLocal.getHttpContext().getHttpSession();
			Long userId = (Long)session.getAttribute(SSOConsts.SESSION_NAME_LOGIN_USER_ID);
			return userId;
		} catch (Exception e) {
			return null;
		}
	}
	
	/**
	 * 获取当前request的登录用户loginId
	 * @return
	 * @author: wanghua
	 */
	public static String getLoginUserName(){
		try {
			HttpSession session = HttpContextThreadLocal.getHttpContext().getHttpSession();
			String userName = (String)session.getAttribute(SSOConsts.SESSION_NAME_LOGIN_NAME);
			return userName;
		} catch (Exception e) {
			return null;
		}
	}
	
	public String getLogonId(){
		return getLoginId();
	}
	
	@Override
	public String getLogonUserId(){
		return getLoginId();
	}
	
	@Override
	public int getAuthTag(String funcCode) throws Exception {
		Long userId = getLoginUserId();
		Assert.throwz(userId != null && userId != 0, "没有获取到登录信息");
		Long tenantId = getAuthTenantId();
		return haveAuthrity(userId, tenantId, funcCode);
	}
	
	/**
	 * 获取当前用户，parentFuncCode下的授权的子节点，仅仅返回这一级的节点，不包括孙节点。
	 * @param parentFuncId
	 * @throws Exception 
	 * @author: wanghua
	 */
	public static List<AppFunctionAuth> getAuthorizedChildrenFunctions(Long tenantId, String parentFuncCode) throws Exception{
		Long userId = getLoginUserId();
		Assert.throwz(userId != null && userId != 0, "没有获取到登录信息");
		String appKey = AppConst.getAppKey(); 
		List<AppFunctionAuth> list  = authorizeService.getAuthorizedChildrenFunctions(userId, tenantId, appKey, parentFuncCode);
		if(list == null)list = new ArrayList<AppFunctionAuth>();
		return list;
	}

	/**
	 * 获取当前用户，所有授权的子节点，包括孙节点。
	 * @param parentFuncId
	 * @throws Exception 
	 * @author: wanghua
	 */
	public static List<AppFunctionAuth> getAuthFunctionTree(Long tenantId) throws Exception{
		return getAuthFunctionTree(tenantId);
	}
	/**
	 * 获取当前用户，parentFuncId下的授权的子节点，包括孙节点。
	 * @param parentFuncId
	 * @throws Exception 
	 * @author: wanghua
	 */
	public static List<AppFunctionAuth> getAuthFunctionTree(String parentFuncCode) throws Exception{
		Long userId = getLoginUserId();
		Assert.throwz(userId != null && userId != 0, "没有获取到登录信息");
		Long tenantId = getAuthTenantId();
		String appKey = AppConst.getAppKey();
		if(parentFuncCode == null || parentFuncCode.length() == 0) {
			parentFuncCode = appKey;
		}
		List<AppFunctionAuth> list  = authorizeService.getAllAuthByTree(userId, tenantId, appKey, parentFuncCode);
		if(list == null)list = new ArrayList<AppFunctionAuth>();
		return list;
	}
	/**
	 * 判断当前用户对funcId是否有操作权限
	 * @param funcId
	 * @return 返回值大于0表示有权限 , 1表示只读， 2表示读写 。小于等于0 表示未授权
	 * @throws Exception 
	 * @author: wanghua
	 */
	public static int  haveAuthrity(String funcCode) throws Exception{
		Long userId = getLoginUserId();
		Assert.throwz(userId != null && userId != 0, "没有获取到登录信息");
		Long tenantId = getAuthTenantId();
		return haveAuthrity(userId, tenantId, funcCode);
	}


	@SuppressWarnings("unchecked")
	private static int haveAuthrity(Long userId, Long tenantId, String funcCode) throws Exception {		
		String appKey = AppConst.getAppKey();
		HttpServletRequest req = HttpContextThreadLocal.getHttpContext().getHttpRequest();	
		String authFuncSessionKey = req.getSession().getId()+"_sso_auth_funcs_8421_wh_" + tenantId;
		QDualCache cache = QDualCache.instance();
		Object obj = cache.getObject(authFuncSessionKey);
		Map<String,AppFuncAuth> authFuncs ; //<FuncId,AppFunctionAuth>
		if(obj != null && obj instanceof Map){
			authFuncs = (Map<String,AppFuncAuth>)obj;
		}else{
			List<AppFunctionAuth> authFuncTree  = authorizeService.getAllAuthByTree(userId, tenantId, appKey, appKey);
			authFuncs = new HashMap<String,AppFuncAuth>();
			AuthFuncTreeToList(authFuncs, authFuncTree);			 
			cache.setObject(authFuncSessionKey, authFuncs, 60 * 60 * 24);
		}
		AppFuncAuth funcAuth = authFuncs.get(funcCode);
		if(funcAuth == null)return 0;
		return (int)funcAuth.getAuthType();
	}
	
	/** 判断是否只读权限
	 * 功能描述：
	 * @param funcId
	 * @return 有权限且为读写权限返回false，否则都返回true
	 * @throws Exception 
	 * @author: wanghua
	 */
	public static boolean  isReadonly(Long tenantId, String funcId) throws Exception{
		int authTag = haveAuthrity(funcId);
		if(authTag == 2)return false; //2表示读写
		return true;
	}

	/**
	 * 将权限树转换为平面的Map结构
	 * @param authFuncs
	 * @param authFuncTree
	 * @author: wanghua
	 */
 
	private static void AuthFuncTreeToList(Map<String,AppFuncAuth> authFuncs,  List<AppFunctionAuth> authFuncTree){
		if(authFuncTree != null && authFuncTree.size()>0){
			for(AppFunctionAuth authFunc : authFuncTree){
				if(authFunc.getAppFunction() != null){
					AppFuncAuth funcAuth = new AppFuncAuth();
					funcAuth.setFuncId(authFunc.getFuncId());
					funcAuth.setAuthType(authFunc.getAuthType());
					authFuncs.put(authFunc.getAppFunction().getFuncCode(), funcAuth);
				}
				AuthFuncTreeToList(authFuncs, authFunc.getChildren());
			}
		}
	}

	/**
	 * 获取系统下的所有的功能，包括所有子、孙节点，其中AppFuncUrl.authType>0时表示此URL已授权能访问
	 * 
	 * 以平行List的方式返回
	 * @param loginId
	 * @param tenantId
	 * @param topFuncCode 系统的FuncCode
	 * @param funcCode
	 * @return 
	 * @author: wanghua
	 */
	public static List<AppFuncUrl> getAuthFunctionUrlList(Long tenantId) throws Exception{
		Long userId = getLoginUserId();
		Assert.throwz(userId != null && userId != 0, "没有获取到登录信息");
		String appKey = AppConst.getAppKey();
		List<AppFuncUrl> urlList = authorizeService.getAuthFunctionUrlList(userId, tenantId, appKey, appKey);
		return urlList;
	}
	/**
	 * 获取系统(当前租户下)下的所有的功能，包括所有子、孙节点，其中AppFuncUrl.authType>0时表示此URL已授权能访问<br>
	 * 本接口会首先从request参数(ssoTenantId)中获取tenantId
	 * 以平行List的方式返回
	 * @param loginId
	 * @param tenantId
	 * @param topFuncCode 系统的FuncCode
	 * @param funcCode
	 * @return 
	 * @author: wanghua
	 */
	public static List<AppFuncUrl> getAuthFunctionUrlList() throws Exception{
		Long userId = getLoginUserId();
		Assert.throwz(userId != null && userId != 0, "没有获取到登录信息");
		Long tenantId = getAuthTenantId();
		String appKey = AppConst.getAppKey();
		List<AppFuncUrl> urlList = authorizeService.getAuthFunctionUrlList(userId, tenantId, appKey, appKey);
		return urlList;
	}
	
	public static boolean resetPassword(String oldPassword, String newPassword) throws Exception{
		String loginId = LoginManager.getLoginId();
		boolean validateMsg = authorizeService.resetPassword(loginId, oldPassword, newPassword);
		return validateMsg;
	}

	/**
	 * 获取SSO系统中的租户ID（逻辑id）
	 * @param customTenantId 客户端的租户id（比如标段id，项目id等）
	 * @param tenantType 租户类型，AuthTenantType中有可用值
	 * @return 
	 * @throws Exception 
	 * @author  wanghua
	 */
	public Long getAuthTenantId(String customTenantId, String tenantType) throws Exception {
		String authCustomTenantId = AuthTenantType.getCustomTenantId(customTenantId, tenantType);
		SSOTenant tenant = authorizeService.getTenantByCustomId(authCustomTenantId);
		if(tenant == null)return null;
		return tenant.getId();
	}
	
	/**从Request参数（ssoTenantId）中获取租户ID（一定不能从session和cookie中获取，一个用户可以属于多个租户，并且能同时打开多个租户的界面）
	 * 
	 * @return 
	 * @author  wanghua
	 */
	public static Long getAuthTenantId() {
		if(!isTenantMode)return 1L;
		Long tenantId = 1L;//1L是内置租户
		String tenantIdKey = SSOConsts.SESSION_NAME_TENANT_ID;
//		HttpServletRequest req = HttpContextThreadLocal.getHttpContext().getHttpRequest();
//		String tenantIdStr = req.getParameter(tenantIdKey);
//		if(tenantIdStr == null || tenantIdStr.length() == 0) {
//			tenantIdStr = req.getHeader(tenantIdKey);
//		}
//		tenantId = Cast.toLong(tenantIdStr, -1L);
		HttpSession session = HttpContextThreadLocal.getHttpContext().getHttpSession();
		tenantId = (Long)session.getAttribute(tenantIdKey);
		Assert.throwz(tenantId != null, "没有从session中获取到租户信息");
		return tenantId;
	}
	
	/**查询当前登录的用户直接以及间接可以访问的租户<br>
	 * 直接访问的租户：用户所属的部门，部门所属的租户<br>
	 * 间接访问的租户：直接租户的子租户（仅一级子租户，不递归）
	 * 
	 * @param tenantType 可以为空，为空时表示所有类型
	 * @return
	 * @throws Exception 
	 * @author  wanghua
	 */
	public static List<SSOTenantEx> getAccessableTenants(String tenantType) throws Exception{
		Long userId = getLoginUserId();
		List<SSOTenantEx> accessableTenants = authorizeService.getAccessableTenants(userId, tenantType);
		return accessableTenants;
	}
	
	/**userId是否有权限访问tenantId（检查用户是否可以访问某个租户）<br>
	 * 1、userId是customTenantId内的用户
	 * 2、userId是customTenantId的父租户内的用户
	 * @param customTenantId
	 * @return
	 * @throws Exception 
	 * @author  wanghua
	 */
	public static boolean isAccessable2Tenant(String customTenantId) throws Exception{
		Long userId = getLoginUserId();
		boolean accessable2Tenant = authorizeService.isAccessable2Tenant(userId, customTenantId);
		return accessable2Tenant;
	}

	/**
	 * 用户userId是否在租户customTenantId的上级租户中。
	 * @param customTenantId
	 * @return
	 * @throws Exception 
	 * @author  wanghua
	 */
	public static boolean isInParentTenant(String customTenantId) throws Exception{
		Long userId = getLoginUserId();
		boolean accessable2Tenant = authorizeService.isInParentTenant(userId, customTenantId);
		return accessable2Tenant;
	}
	
	public static List<UserBase> getAllUsers(){
		return authorizeService.getAllUsers();
	}
	
	/**是否多租户模式*/
	private static boolean isTenantMode;

	public static boolean isTenantMode() {
		return isTenantMode;
	}
	
	@Value("${app.isTenantMode}")
	public void setTenantMode(boolean isTenantMode) {
		LoginManager.isTenantMode = isTenantMode;
	}
}
